import { ShapeFlags } from "./vnode"
import { patchProps } from './patchProps'
import { mountComponent } from './component'

export function render(vnode, container) {
    console.log('888');
    console.log(vnode);
    console.log(container);
    const prevVNode = container._vnode
    if (!vnode) {

        // 如果新vnode不存在，直接卸载旧vnode
        if (prevVNode) {
            unmount(prevVNode)
        }
    } else {
        // 新vnode存在，直接patch打补丁
        patch(prevVNode, vnode, container)
    }
    container._vnode = vnode
}


// patch也有挂载的功能，n1为空的时候
// n1旧节点 n2新节点 container容器
function patch(n1, n2, container, anchor) {
    if (n1 && !isSameVNode(n1, n2)) {
        anchor = (n1.anchor || n1.el).nextSibling
        // 存在n1并且，n1n2不同类型
        unmount(n1)
        n1 = null
    }

    const { shapeFlag } = n2
    if (shapeFlag & ShapeFlags.COMPONENT) {
        console.log('zujian');
        processComponent(n1, n2, container, anchor)
    } else if (shapeFlag & ShapeFlags.TEXT) {
        processText(n1, n2, container, anchor)
    } else if (shapeFlag & ShapeFlags.FRAGMENT) {
        processFragment(n1, n2, container, anchor)
    } else if (shapeFlag & ShapeFlags.ELEMENT) {
        processElement(n1, n2, container, anchor)
    }
}



// 加载新的组件类型节点
function processComponent(n1, n2, container, anchor) {
    if (n1) {
        // 子组件组件是否要更新
        // shouldComponentUpdate
        // 更新组件节点
        updateComponent(n1, n2)
    } else {
        mountComponent(n2, container, anchor, patch)
    }
}

function processText(n1, n2, container, anchor) {
    if (n1) {
        n2.el = n1.el
        n1.el.textContent = n2.children
    } else {
        // 如果旧节点不存在，直接挂载
        mountTextNode(n2, container, anchor)
    }
}

function processFragment(n1, n2, container, anchor) {
    // 因为fragment类型没有真实DOM，所以也就没有el。
    // 创建两个文本节点，一个当作vnode的el，一个当作vnode的anchor
    // 把要添加的元素，插入到这两个中间
    // 如果n1有了这两个文本节点，则不需要设置直接复用，负责要创建
    const fragmentStartAnchor = (n2.el = n1 ? n1.el : document.createTextNode(''))
    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : document.createTextNode(''))

    if (n1) {
        patchChildren(n1, n2, container, fragmentEndAnchor)
    } else {
        container.insertBefore(fragmentStartAnchor, anchor)
        container.insertBefore(fragmentEndAnchor, anchor)
        // 要把真正添加的fragment里的元素，添加到fragmentEndAnchor之前
        mountChildren(n2.children, container, fragmentEndAnchor)
    }
}

function processElement(n1, n2, container, anchor) {
    if (n1) {
        // n1存在，打补丁
        patchElement(n1, n2)
    } else {
        // 不存在，直接挂载新的
        mountElement(n2, container, anchor)
    }
}

function updateComponent(n1, n2) {
    // 取出n1的实例赋值到n2身上，然后进行update
    n2.component = n1.component
    n2.component.next = n2
    n2.component.update()
}

function patchElement(n1, n2) {
    n2.el = n1.el
    // 属性打补丁
    patchProps(n1.props, n2.props, n2.el)
    // 子节点打补丁
    patchChildren(n1, n2, n2.el)
}



// 给子节点打补丁
function patchChildren(n1, n2, container, anchor) {
    const { shapeFlag: prevShapeFlag, children: c1 } = n1
    const { shapeFlag, children: c2 } = n2
    console.log(c1, c2);
    // 新节点是文本类型节点
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {

        if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            // 如果旧子节点是数组类型，先卸载，然后更新新节点
            unmountChildren(c1)
        }
        // 新旧子节点都是文本类型，直接更新
        // 如果c1=c2不做改变，直接复用
        if (c1 !== c2) {
            container.textContent = c2
        }
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
            // 如果旧节点是文本类型，直接删除
            container.textContent = ''
            mountChildren(c2, container, anchor)
        } else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            // 只要第一个元素有key值，那么就当作都有key
            if (c1[0] && c1[0].key != null && c2[0] && c2[0].key != null) {
                // 处理有key的节点
                patchKeyedChildren(c1, c2, container, anchor)
            } else {
                // 处理没有key的节点
                patchUnKeyedChildren(c1, c2, container, anchor)
            }
        } else {
            // 旧节点不存在，直接更新
            mountChildren(c2, container, anchor)
        }
    } else {

        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
            container.textContent = ''
        } else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            unmountChildren(c1)
        }
    }
}
// 处理有key的节点

// 快速diff算法
function patchKeyedChildren(c1, c2, container, anchor) {
    // 定义开头指向新旧两组子节点的开头
    let i = 0
    // 指向新旧两组子节点末尾
    let e1 = c1.length - 1
    let e2 = c2.length - 1
    // 1.从左到右依次比对
    while (i <= e1 && i <= e2 && c1[i].key === c2[i].key) {
        // 如果新旧节点相同key值，直接更新
        patch(c1[i], c2[i], container, anchor)
        i++
    }

    // 2.从右到左依次比对
    while (i <= e1 && i <= e2 && c1[e1].key === c2[e2].key) {
        patch(c1[e1], c2[e2], container, anchor)
        e1--
        e2--
    }

    // c1:a b c
    // c2:a d b c
    // 经过比对后
    // i = 1
    // e1 = 0
    // e2 = 1
    // i>e1这种情况是旧节点处理完毕，如果有新增的节点直接mount
    if (i > e1 && i <= e2) {
        // 旧节点处理完毕
        // 3.经过1、2把新旧节点对比完，剩下的新节点直接挂载
        for (let j = i; j <= e2; j++) {
            // 锚点元素
            const nextPos = e2 + 1
            const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
            patch(null, c2[j], container, curAnchor)
        }
    } else if (i > e2 && i <= e1) {

        // 3.新旧节点比对完毕，中间没有相同的要unmount
        for (let j = i; i <= e1; j++) {
            unmount(c1[j])
        }

    } else {
        // 非理性情况
        // 4.若中间有相等的，要使用传统diff算法,但不真的添加和移动，只做标记和删除
        // eg:
        // 1 2 3 4 5 6
        // 1 3 4 2 7 6
        const map = new Map()
        for (let j = i; j <= e1; j++) {
            const prev = c1[j]
            map.set(prev.key, { prev, j })
        }
        let maxNewIndexSoFar = 0
        let move = false
        // 定义source数组，用来存储新节点在旧节点里的索引
        const source = new Array(e2 - i + 1).fill(-1)
        const toMounted = []
        // 遍历新的子节点
        for (let k = 0; k < source.length; k++) {
            const next = c2[k + i]

            if (map.has(next.key)) {
                const { prev, j } = map.get(next.key)
                patch(prev, next, container, anchor)
                // const curAnchor = c1[0].el
                // const curAnchor = c2[i - 1].el.nextSibling
                // 把旧的子节点插到第一个位置，第一次的时候curAnchor是旧的第一个DOM

                // diff算法移动的条件：在旧DOM列表中寻找当前节点的引用，如果他的索引大于当前的maxIndex,不需要移动这个DOM，要把这个索引设置为当前maxIndex
                // 如果在旧DOM的索引小于当前的maxIndex就要移动
                // 升序不需要移动
                if (j < maxNewIndexSoFar) {
                    // 需要移动
                    move = true
                    // container.insertBefore(next.el, curAnchor)
                } else {
                    maxNewIndexSoFar = j
                }
                source[k] = j
                // 如果找到了就把这个节点从map中移除，剩下的都是新DOM中没有的
                map.delete(next.key)
            } else {
                // todo
                toMounted.push(k + i)
            }

        }

        // 这层遍历是为了把旧DOM中有 新DOM中没有的节点去除
        map.forEach(({ prev }) => {
            unmount(prev)
        })
        if (move) {
            // 5.需要移动，使用最长上升子序列算法
            const seq = getSequence(source)
            // 指向seq最长上升子序列最后一个元素
            let j = seq.length - 1
            // 指向新的一组子节点的最后一个元素，经过前后置比对后的
            let k = source.length - 1
            for (k; k >= 0; k--) {
                if (seq[j] === k) {
                    // 不用移动
                    j--
                } else {
                    const pos = k + i
                    const nextPos = pos + 1
                    const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
                    if (source[k] === -1) {
                        // 如果是-1则说明是全新的节点，需要mount
                        patch(null, c2[pos], container, curAnchor)

                    } else {
                        // 移动
                        container.insertBefore(c2[pos].el, curAnchor)

                    }
                }
            }
        } else if (toMounted.length) {
            // 6.特殊情况：不需要移动，但是有未添加的元素
            for (let k = toMounted.length - 1; k >= 0; k--) {
                const pos = toMounted[k]
                const nextPos = pos + 1
                const curAnchor = (c2[nextPos] && c2[nextPos].el) || anchor
                patch(null, c2[pos], container, curAnchor)
            }
        }
    }

}

// 最长上升子序列算法
function getSequence(nums) {
    const arr = [nums[0]];
    const position = [0];
    for (let i = 1; i < nums.length; i++) {
        if (nums[i] === -1) {
            continue;
        }
        if (nums[i] > arr[arr.length - 1]) {
            arr.push(nums[i]);
            position.push(arr.length - 1);
        } else {
            let l = 0,
                r = arr.length - 1;
            while (l <= r) {
                let mid = ~~((l + r) / 2);
                if (nums[i] > arr[mid]) {
                    l = mid + 1;
                } else if (nums[i] < arr[mid]) {
                    r = mid - 1;
                } else {
                    l = mid;
                    break;
                }
            }
            arr[l] = nums[i];
            position.push(l);
        }
    }

    let cur = arr.length - 1;
    for (let i = position.length - 1; i >= 0 && cur >= 0; i--) {
        if (position[i] === cur) {
            arr[cur--] = i;
        }
    }
    return arr;

}

// 处理没有key的节点:暴力diff
function patchUnKeyedChildren(c1, c2, container, anchor) {
    const oldLength = c1.length
    const newLength = c2.length
    const commonLength = Math.min(oldLength, newLength)
    for (let i = 0; i < commonLength; i++) {
        patch(c1[i], c2[i], container, anchor)
    }
    if (oldLength > newLength) {
        unmountChildren(c1.slice(commonLength))
    } else if (oldLength < newLength) {
        mountChildren(c2.slice(commonLength), container, anchor)
    }
}
// 处理有key的节点 简单diff算法
// function patchKeyedChildren(c1, c2, container, anchor) {
//     const map = new Map()
//     c1.forEach((prev, j) => {
//         map.set(prev.key, { prev, j })
//     })
//     let maxNewIndexSoFar = 0
//     // 遍历新的子节点
//     for (let i = 0; i < c2.length; i++) {
//         const next = c2[i]

//         if (map.has(next.key)) {
//             const { prev, j } = map.get(next.key)
//             patch(prev, next, container, anchor)
//             // const curAnchor = c1[0].el
//             // const curAnchor = c2[i - 1].el.nextSibling
//             // 把旧的子节点插到第一个位置，第一次的时候curAnchor是旧的第一个DOM

//             // diff算法移动的条件：在旧DOM列表中寻找当前节点的引用，如果他的索引大于当前的maxIndex,不需要移动这个DOM，要把这个索引设置为当前maxIndex
//             // 如果在旧DOM的索引小于当前的maxIndex就要移动
//             // 升序不需要移动
//             if (j < maxNewIndexSoFar) {
//                 // 需要移动
//                 const curAnchor = c2[i - 1].el.nextSibling
//                 container.insertBefore(next.el, curAnchor)
//             } else {
//                 maxNewIndexSoFar = j
//             }

//             // 如果找到了就把这个节点从map中移除，剩下的都是新DOM中没有的
//             map.delete(next.key)
//         } else {
//             // 拿到当前这个新增的DOM，然后进行插入
//             const curAnchor = i === 0 ? c1[0].el : c2[i - 1].el.nextSibling
//             patch(null, next, container, curAnchor)
//         }

//     }

//     // 这层遍历是为了把旧DOM中有 新DOM中没有的节点去除
//     map.forEach(({ prev }) => {
//         unmount(prev)
//     })

// }


// 挂载
function mountTextNode(vnode, container, anchor) {
    const textNode = document.createTextNode(vnode.children)
    // 如果anchor为undefined，则第一个参数会插在container的末尾
    container.insertBefore(textNode, anchor)
    vnode.el = textNode
}

function mountElement(vnode, container, anchor) {
    // type是元素节点的标签
    const { type, props, shapeFlag, children } = vnode
    const el = document.createElement(type)
    // 把props属性挂到el上
    // mountProps(props, el)
    patchProps(null, props, el)

    // 如果子节点是文本节点
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
        mountTextNode(vnode, el)
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(children, el)
    } else {
        // todo 做了一些调整
        // console.log(el)
        // // 把子节点挂载到el上
        // mountChildren(vnode, el)
    }

    // 把最终的节点挂载到container中
    // 如果anchor为undefined，则是加到最后面
    container.insertBefore(el, anchor)
    // 把生成的真实DOM节点放在vnode的el上
    vnode.el = el
}

function mountChildren(children, container, anchor) {
    children.forEach(child => {
        // mount(child, container)
        // patch有挂载的功能
        patch(null, child, container, anchor)
    })
}



// 卸载

// 卸载子节点
function unmountChildren(children) {
    children.forEach((child) => {
        unmount(child)
    })
}

// 把节点卸载
function unmount(vnode) {
    const { shapeFlag, el } = vnode
    // 判断是什么类型的虚拟DOM
    if (shapeFlag & ShapeFlags.COMPONENT) {
        unmountComponent(vnode)
    } else if (shapeFlag & ShapeFlags.FRAGMENT) {
        unmountFragment(vnode)
    } else {
        // 卸载元素DOM和文本DOM
        el.parentNode.removeChild(el)
    }
}

// 卸载组件
function unmountComponent(vnode) {
    unmount(vnode.component.subTree)
}

// 卸载Fragment
function unmountFragment(vnode) {
    // todo 
    let { el: cur, anchor: end } = vnode
    const { parentNode } = cur
    while (cur !== end) {
        // nextSibling下一个兄弟节点
        let next = cur.nextSibling
        parentNode.removeChild(cur)
        cur = next
    }
    parentNode.removeChild(end)
}

// 辅助函数
function isSameVNode(n1, n2) {
    return n1.type === n2.type
}